using System;
using System.Collections.Generic;
using System.Text;

namespace ChipSharp
{
    /*
     * <Summary>
     * Movable object
     * </Summary>
     */

    class Movable : Charp
    {
        /* Mass */
        public double Mass { get; set; }

        public World.Direction VDir = World.Direction.kNone;   // direction of moving
        public double TicksToNext = 0.0;     // total ticks for one step
        public double TicksRemaining = 0.0;

        public bool Processed { get; set; }

        public Movable(int x, int y) 
            : base(x, y)
        {
        }

        public override void Draw(System.Drawing.Graphics graph, System.Drawing.Rectangle rect)
        {
            base.Draw(graph, rect);
        }

        protected void Move(World world)
        {
            Charp[, ] map = world.Map;
            int oldx = X;
            int oldy = Y;
            switch (VDir)
            {
                case World.Direction.kLeft:
                    X--;
                    if (X < 0) X = 0;
                    break;
                case World.Direction.kRight:
                    X++;
                    int xMax = map.GetUpperBound(1);
                    if (X > xMax) X = map.GetUpperBound(1);
                    break;
                case World.Direction.kUp:
                    Y--;
                    if (Y < 0) Y = 0;
                    break;
                case World.Direction.kDown:
                    Y++;
                    int yMax = map.GetUpperBound(0);
                    if (Y > yMax) Y = map.GetUpperBound(0);
                    break;
            }

            if (oldy != Y || oldx != X)
            {
                map[oldy, oldx].Remove(this);
                map[Y, X].AddToTop(this);
            }
        }

        protected void CheckStep(World world)
        {
            switch (this.VDir)
            {
                case World.Direction.kDown:
                    if (this.Y == world.Map.GetUpperBound(0)
                        || world.Map[this.Y, this.X].Block(true, this.VDir)
                        || world.Map[this.Y + 1, this.X].Block(false, this.VDir) )
                    {
                        this.VDir = World.Direction.kUp;
                        TicksRemaining = TicksToNext;
                    }
                    break;
                case World.Direction.kLeft:
                    if (this.X == world.Map.GetLowerBound(1)
                        || world.Map[this.Y, this.X].Block(true, this.VDir)
                        || world.Map[this.Y, this.X - 1].Block(false, this.VDir))
                    {
                        this.VDir = World.Direction.kRight;
                        TicksRemaining = TicksToNext;
                    }
                    break;
                case World.Direction.kRight:
                    if (this.X == world.Map.GetUpperBound(1)
                        || world.Map[this.Y, this.X].Block(true, this.VDir)
                        || world.Map[this.Y, this.X + 1].Block(false, this.VDir))
                    {
                        this.VDir = World.Direction.kLeft;
                        TicksRemaining = TicksToNext;
                    }
                    break;
                case World.Direction.kUp:
                    if (this.Y == world.Map.GetLowerBound(0)
                        || world.Map[this.Y, this.X].Block(true, this.VDir)
                        || world.Map[this.Y - 1, this.X].Block(false, this.VDir))
                    {
                        this.VDir = World.Direction.kDown;
                        TicksRemaining = TicksToNext;
                    }
                    break;
            }
        }

        public override void Step(World world)
        {
            CheckStep(world);

            if (VDir == World.Direction.kNone || TicksToNext <= 0.0)
            {
                TicksToNext = 0.0;
                return;
            }

            TicksRemaining--;
            if (TicksRemaining > 0.0)
            {
                return;
            }

            // make a free move
            Move(world);

            TicksRemaining += TicksToNext;
        }

        protected void SolveElasticClash(double w1, double w2, ref double v1, ref double v2)
        {
            double p1 = w1 * v1;
            double p2 = w2 * v2;
            double p = p1 + p2;
            double ex = p1 * v1 + p2 * v2;

            double a = (w1 + w2) * w2;
            double b = -2 * w2;
            double c = p * p - w1 * ex;
            double delta = b * b - 4 * a * c;

            double v21 = (-b + Math.Sqrt(delta)) / (2 * a);
            double v22 = (-b - Math.Sqrt(delta)) / (2 * a);

            v2 = Math.Abs(v21 - v2) < Math.Abs(v22 - v2) ? v22 : v21;
            v1 = (p - w2 * v2) / w1;
        }

        public virtual void Collide(Movable that)
        {
            double vThis = 1.0 / TicksToNext;
            double vxThis = 0.0, vyThis = 0.0;
            switch (VDir)
            {
                case World.Direction.kLeft:
                    vxThis = -vThis;
                    break;
                case World.Direction.kRight:
                    vxThis = vThis;
                    break;
                case World.Direction.kUp:
                    vyThis = -vThis;
                    break;
                case World.Direction.kDown:
                    vyThis = vThis;
                    break;
            }

            double vThat = 1.0 / that.TicksToNext;
            double vxThat = 0.0, vyThat = 0.0;
            switch (that.VDir)
            {
                case World.Direction.kLeft:
                    vyThat = -vThat;
                    break;
                case World.Direction.kRight:
                    vyThat = vThat;
                    break;
                case World.Direction.kUp:
                    vyThat = -vThat;
                    break;
                case World.Direction.kDown:
                    vyThat = vThat;
                    break;
            }

            if (vxThis != 0 || vxThat != 0)
            {
                SolveElasticClash(this.Mass, that.Mass, ref vxThis, ref vxThat);

                this.TicksToNext = 1.0 / vxThis;
                that.TicksToNext = 1.0 / vxThat;

                // should be zero on y direction, return here
            }
            else if (vyThis != 0 || vyThat != 0)
            {
                SolveElasticClash(this.Mass, that.Mass, ref vyThis, ref vyThat);

                this.TicksToNext = 1.0 / vyThis;
                that.TicksToNext = 1.0 / vyThat;
            }
        }
    }
}
